package com.yifeng.repo.micro.service.server.helper;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.enums.SqlKeyword;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.gomcarter.frameworks.base.common.ReflectionUtils;
import com.gomcarter.frameworks.base.pager.DefaultPager;
import com.google.common.base.Strings;
import com.yifeng.repo.base.annotation.LambdaCondition;
import com.yifeng.repo.base.annotation.LambdaOperator;
import com.yifeng.repo.base.api.PageResult;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.boot.context.properties.ConfigurationProperties;

import java.beans.Introspector;
import java.lang.invoke.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;

import static java.lang.invoke.LambdaMetafactory.FLAG_SERIALIZABLE;

/**
 * Created by daibing on 2023/6/13.
 */
public class FesLambdaTransformer {
    private static final ConcurrentMap<String, SFunction> entityClassField2Function = new ConcurrentHashMap<>();
    private static final ConcurrentMap<Class<?>, List<Field>> paramsClass2Fields = new ConcurrentHashMap<>();
    private static final String OPENX_FIELD_NAME = "__PARANAMER_DATA";

    /**
     * count 查询包装器：不做参数排除
     */
    public static <T, R> LambdaQueryWrapper<T> buildCountWrapper(Class<T> entityClass, R params) {
        return buildCountWrapper(entityClass, params, Collections.emptyList());
    }

    /**
     * count 查询包装器：根据指定参数做排除
     */
    public static <T, R> LambdaQueryWrapper<T> buildCountWrapper(Class<T> entityClass, R params, List<SFunction<T, ?>> excludeConditionColumns) {
        LambdaQueryWrapper<T> lambdaWrapper = Wrappers.lambdaQuery();
        buildQueryCondition(lambdaWrapper, entityClass, params, excludeConditionColumns);
        return lambdaWrapper;
    }

    public static <T, R> LambdaQueryWrapper<T> buildContainsWrapper(Class<T> entityClass, R params, SFunction<T, ?> keyColumn) {
        LambdaQueryWrapper<T> lambdaWrapper = buildCountWrapper(entityClass, params, Collections.emptyList());
        lambdaWrapper.select(Collections.singletonList(keyColumn));
        lambdaWrapper.orderByAsc(keyColumn);
        return lambdaWrapper;
    }

    /**
     * 普通查询包装器：强制带分页查询
     */
    public static <T, R> LambdaQueryWrapper<T> buildQueryWrapper(Class<T> entityClass, R params) {
        return buildQueryWrapper(entityClass, params, Collections.emptyList(), Collections.emptyList());
    }

    /**
     * 普通查询包装器：强制带分页查询
     */
    public static <T, R> LambdaQueryWrapper<T> buildQueryWrapper(Class<T> entityClass, R params,
                                                                 List<SFunction<T, ?>> selectColumns, List<SFunction<T, ?>> excludeConditionColumns) {
        LambdaQueryWrapper<T> lambdaWrapper = Wrappers.lambdaQuery();
        buildQueryCondition(lambdaWrapper, entityClass, params, excludeConditionColumns);
        buildQueryColumn(lambdaWrapper, selectColumns);
        return lambdaWrapper;
    }

    public static <T, R> LambdaQueryWrapper<T> buildQueryWrapper(SFunction<T, ?> offsetColumn, Long offsetId) {
        LambdaQueryWrapper<T> lambdaWrapper = Wrappers.lambdaQuery();
        lambdaWrapper.gt(offsetColumn, offsetId);
        lambdaWrapper.orderByAsc(offsetColumn);
        return lambdaWrapper;
    }

    /**
     * 分页查询参数，当前默认排序是"id"，可以在业务代码修改排序id
     */

    public static <T> Page<T> buildPageParam(int batchSize) {
        return new Page<>(1, batchSize, false);
    }

    public static <T> Page<T> buildPageParam(DefaultPager pager) {
        return buildPageParam(pager, true);
    }

    public static <T> Page<T> buildPageParam(DefaultPager pager, boolean searchCount) {
        Page<T> page = new Page<>(pager.getStartNum() / pager.getPageCount() + 1, pager.getPageCount(), searchCount);
        page.addOrder(pager.getOrder().equalsIgnoreCase(SqlKeyword.ASC.name()) ? OrderItem.asc(pager.getSort()) : OrderItem.desc(pager.getSort()));
        return page;
    }

    public static <T, R> LambdaQueryWrapper<T> buildQueryColumn(LambdaQueryWrapper<T> wrapper, List<SFunction<T, ?>> selectColumns) {
        boolean hasColumn = selectColumns != null && !selectColumns.isEmpty();
        if (hasColumn) {
            wrapper.select(selectColumns);
        }
        return wrapper;
    }


    public static <T, R> LambdaQueryWrapper<T> buildQueryCondition(LambdaQueryWrapper<T> wrapper,
                                                                   Class<T> entityClass, R params, List<SFunction<T, ?>> excludeConditionColumns) {
        // 获取查询参数对象的字段
        List<Field> fields = listParamsField(params.getClass());

        // 排除字段方法引用转换为字段名称
        List<String> excludeConditionColumnNames = excludeConditionColumns.stream().map(FesLambdaTransformer::getFieldName).collect(Collectors.toList());

        // 遍历params的字段
        for (Field field : fields) {
            // 1. 检查是否 openx 生成字段
            if (field.getName().equals(OPENX_FIELD_NAME)) {
                continue;
            }

            // 2. 获取查询参数字段对应的entity字段名称，以及对应的操作符
            String entityName = field.getName();
            LambdaOperator queryOperator = LambdaOperator.EQ;
            if (field.isAnnotationPresent(LambdaCondition.class)) {
                LambdaCondition queryCondition = field.getAnnotation(LambdaCondition.class);
                if (!Strings.isNullOrEmpty(queryCondition.entityName())) {
                    entityName = queryCondition.entityName();
                }
                queryOperator = queryCondition.queryOperator();
            }

            // 3. 检查查询参数字段是否在排除条件范围内
            if (LambdaOperator.REMOVE_PARAM == queryOperator || excludeConditionColumnNames.contains(entityName)) {
                continue;
            }

            // 4. 构造查询条件第一步（不需要获取入参对象的字段值）（具备通过反射来调用方法的条件，LambdaOperator枚举带方法名，暂时不考虑）
            SFunction<T, ?> entityFunc = getLambdaFunction(entityClass, entityName);
            switch (queryOperator) {
                case IS_NULL:
                    wrapper.isNull(entityFunc);
                    break;
                case IS_NOT_NULL:
                    wrapper.isNotNull(entityFunc);
                    break;
                case GROUP_BY:
                    wrapper.groupBy(entityFunc);
                    break;
//                case ORDER_BY_ASC:
//                    wrapper.orderByAsc(entityFunc);
//                    break;
//                case ORDER_BY_DESC:
//                    wrapper.orderByDesc(entityFunc);
//                    break;
            }

            // 5. 构造查询条件第二步（需要获取入参对象的字段值）
            Object value = ReflectionUtils.getFieldValue(params, field);
            if (value != null) {
                switch (queryOperator) {
                    case IN:
                        wrapper.in(entityFunc, toArray(value));
                        break;
                    case NOT_IN:
                        wrapper.notIn(entityFunc, toArray(value));
                        break;
                    case LIKE:
                        wrapper.like(entityFunc, value);
                        break;
                    case NOT_LIKE:
                        wrapper.notLike(entityFunc, value);
                        break;
                    case LIKE_RIGHT:
                        wrapper.likeRight(entityFunc, value);
                        break;
                    case LIKE_LEFT:
                        wrapper.likeLeft(entityFunc, value);
                        break;
                    case NOT_LIKE_RIGHT:
                        wrapper.notLikeRight(entityFunc, value);
                        break;
                    case NOT_LIKE_LEFT:
                        wrapper.notLikeLeft(entityFunc, value);
                        break;
                    case EQ:
                        wrapper.eq(entityFunc, value);
                        break;
                    case NE:
                        wrapper.ne(entityFunc, value);
                        break;
                    case GT:
                        wrapper.gt(entityFunc, value);
                        break;
                    case GE:
                        wrapper.ge(entityFunc, value);
                        break;
                    case LT:
                        wrapper.lt(entityFunc, value);
                        break;
                    case LE:
                        wrapper.le(entityFunc, value);
                        break;
                    default:
                        throw new UnsupportedOperationException("Waiting to do: " + queryOperator.name());
                }
            }
        }
        return wrapper;
    }

    private static Object[] toArray(Object value) {
        if (value instanceof List) {
            return ((ArrayList<?>) value).toArray();
        }
        if (value instanceof Set) {
            return ((HashSet<?>) value).toArray();
        }
        return new Object[]{value};
    }

    private static List<Field> listParamsField(Class<?> clazz) {
        if (paramsClass2Fields.containsKey(clazz)) {
            return paramsClass2Fields.get(clazz);
        }
        synchronized (paramsClass2Fields) {
            if (paramsClass2Fields.containsKey(clazz)) {
                return paramsClass2Fields.get(clazz);
            }
            paramsClass2Fields.put(clazz, ReflectionUtils.findAllField(clazz));
            return paramsClass2Fields.get(clazz);
        }
    }

    /**
     * 根据实体类的字段名称获取Lambda功能接口，注意entity中方法名称：Boolean对象是get/set开头，boolean是is/set开头
     *
     * @param entityClass 比如实体对象 UserEntity
     * @param fieldName   比如实体对象的字段id
     * @return UserEntity::getId
     */
    @SuppressWarnings({"rawtypes", "unchecked"})
    public static <T> SFunction<T, ?> getLambdaFunction(Class<T> entityClass, String fieldName) {
        String key = entityClass.getName() + fieldName;
        if (entityClassField2Function.containsKey(key)) {
            return entityClassField2Function.get(key);
        }
        synchronized (entityClassField2Function) {
            // 1. 第二次检查缓存
            if (entityClassField2Function.containsKey(key)) {
                return entityClassField2Function.get(key);
            }

            // 2. 反射获取字段
            Field field = null;
            try {
                field = entityClass.getDeclaredField(fieldName);
            } catch (NoSuchFieldException e) {
                throw new RuntimeException(e);
            }

            // 3. 获取功能接口
            MethodHandles.Lookup lookup = MethodHandles.lookup();
            MethodType methodType = MethodType.methodType(field.getType(), entityClass);
            String methodName = getMethodName(field);
            try {
                CallSite site = LambdaMetafactory.altMetafactory(
                        lookup,
                        "invoke",
                        MethodType.methodType(SFunction.class),
                        methodType,
                        lookup.findVirtual(entityClass, methodName, MethodType.methodType(field.getType())),
                        methodType,
                        FLAG_SERIALIZABLE);
                SFunction func = (SFunction) site.getTarget().invokeExact();
                entityClassField2Function.put(key, func);
                return func;
            } catch (Throwable t) {
                throw new RuntimeException(t);
            }
        }
    }

    private static String getMethodName(Field field) {
        String fieldName = field.getName();
        if (!field.getType().equals(boolean.class)) {
            return "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
        }
        if (fieldName.startsWith("is")) {
            return fieldName;
        }
        return "is" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
    }

    /**
     * 根据Lambda功能接口获取字段名
     *
     * @param func 比如 UserEntity::getId
     * @return 比如 id
     */
    public static <T> String getFieldName(SFunction<T, ?> func, String prefix) {
        try {
            // 当一个java类实现了Serializable接口时，便自动赋能了几个隐藏方法，包括writeReplace
            Method method = func.getClass().getDeclaredMethod("writeReplace");
            method.setAccessible(Boolean.TRUE);
            SerializedLambda serializedLambda = (SerializedLambda) method.invoke(func);
            String getterMethod = serializedLambda.getImplMethodName();
            return Introspector.decapitalize(getterMethod.replaceFirst(prefix, ""));
        } catch (ReflectiveOperationException e) {
            throw new RuntimeException(e);
        }
    }

    public static <T> String getFieldName(SFunction<T, ?> func) {
        try {
            // 当一个java类实现了Serializable接口时，便自动赋能了几个隐藏方法，包括writeReplace
            Method method = func.getClass().getDeclaredMethod("writeReplace");
            method.setAccessible(Boolean.TRUE);
            SerializedLambda serializedLambda = (SerializedLambda) method.invoke(func);
            String getterMethod = serializedLambda.getImplMethodName();
            String prefix = getterMethod.startsWith("is") ? "is" : "get";
            return Introspector.decapitalize(getterMethod.replaceFirst(prefix, ""));
        } catch (ReflectiveOperationException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取springboot配置文件路径
     *
     * @param func   ServerEngineDaoProperties::getDsRoutingType
     * @param prefix "get"
     * @return yfcloud.micro.service.engine.dao.ds-routing-type
     */
    public static <T> String getEnvironmentKey(SFunction<T, ?> func, String prefix) {
        try {
            // 当一个java类实现了Serializable接口时，便自动赋能了几个隐藏方法，包括writeReplace
            Method method = func.getClass().getDeclaredMethod("writeReplace");
            method.setAccessible(Boolean.TRUE);
            SerializedLambda serializedLambda = (SerializedLambda) method.invoke(func);
            Class<?> clazz = Class.forName(serializedLambda.getImplClass().replaceAll("/", "."));
            String fieldName = Introspector.decapitalize(serializedLambda.getImplMethodName().replaceFirst(prefix, ""));
            return clazz.getDeclaredAnnotation(ConfigurationProperties.class).value() + "." + StringUtils.camelToHyphen(fieldName);
        } catch (ReflectiveOperationException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 扩展 org.springframework.beans.copyProperties
     */
    @SafeVarargs
    public static <T> T copyProperties(Object source, T target, SFunction<T, ?>... ignoreProperties) throws BeansException {
        if (source == null) {
            return null;
        }
        if (ignoreProperties == null || ignoreProperties.length == 0) {
            BeanUtils.copyProperties(source, target);
        } else {
            String[] ignoreNameProperties = Arrays.stream(ignoreProperties).map(FesLambdaTransformer::getFieldName).toArray(String[]::new);
            BeanUtils.copyProperties(source, target, ignoreNameProperties);
        }
        return target;
    }

    /**
     * 拷贝分页对象：
     *
     * @param page   待拷贝原始分页对象
     * @param mapper 分页对象里面的数据元素拷贝逻辑
     */
    public static <T, R> PageResult<R> copyPage(Page<T> page, SFunction<T, R> mapper) {
        PageResult<R> result = new PageResult<>((int) page.getCurrent(), (int) page.getSize());
        result.setTotalPage((int) page.getPages());
        result.setTotalSize((int) page.getTotal());
        result.setResult(page.getRecords().stream().map(mapper).collect(Collectors.toList()));
        return result;
    }
}
